#!/usr/bin/env perl
use strict;
use FindBin;

use File::Basename;
use Cwd 'realpath';
use Getopt::Long;
use JSON;

use ServerAdapter;
use SyncLocal2Remote;
use SyncRemote2Local;
use DeployUtils;
use DeployLock;

sub usage {
    my $pname = $FindBin::Script;

    print("Usage: $pname [--envpath EnvPath] [--version VERSION] [--verbose 0|1]\n");
    print("              [--pdir approot|project|release|appdist|dbscript|mirror]\n");
    print("              [--ostype windows|unix]\n");
    print("              [--nodelete 0|1] [--noattrs 0|1] [--followlinks 0|1]\n");
    print("              [--addexecfornewfile 0|1] [-x ExcludeSubDirs] [--pull 0|1]\n");
    print("              [--src SourceSubDir] [--dest ServerSitePath]\n");
    print("\n");
    print("       sync files to remote server\n");
    print("       --envpath:           Env path in the data directory, example:ATM/ATMP/PRD/ATMP-1\n");
    print("       --version:           version number of sub system\n");
    print("       --pull:              pull from remote site to local site\n");
    print("       --pdir:              sub directory type of this system\n");
    print("       --nodelete:          do not delete server site file\n");
    print("       --noattrs:           do not sync file attributes\n");
    print("       --followlinks:       symbol link as directory\n");
    print("       --addexecfornewfile: add execute permission on new file of server side\n");
    print("       --x:                 do not symc sub dir or files, example:sub1/sub2,sub3\n");
    print("       --src:               sub directory in pdir\n");
    print("       --dest:              directory on server side\n");

    exit(1);
}

sub main {
    my ( $isHelp, $isVerbose, $envPath, $version, $node );
    my ( $ostype, $pdir );
    my ( $host,   $port, $user, $pass, $src, $dest, $expDir );
    my $isPull            = 0;
    my $noDelete          = 0;
    my $deleteOnly        = 0;
    my $opMode            = 0;
    my $noAttrs           = 0;
    my $addExecForNewFile = 0;
    my $followLinks       = 0;
    my $pname             = $FindBin::Script;

    $isVerbose = 0;
    $pdir      = undef;

    GetOptions(
        'v|verbose=i'         => \$isVerbose,
        'h|help'              => \$isHelp,
        'ostype=s'            => \$ostype,
        'envpath=s'           => \$envPath,
        'version=s'           => \$version,
        'pdir=s'              => \$pdir,
        'nodelete=i'          => \$noDelete,
        'opmode=i'            => \$opMode,
        'noattrs=i'           => \$noAttrs,
        'followlinks=i'       => \$followLinks,
        'addexecfornewfile=i' => \$addExecForNewFile,
        'pull=i'              => \$isPull,
        'x|exp=s'             => \$expDir,
        'src=s'               => \$src,
        'dest=s'              => \$dest,
        'node=s'              => \$node
    );

    my $optionError = 0;
    if ( not defined($dest) ) {
        print("ERROR: Must defined option --dest.\n");
        $optionError = 1;
    }

    if ( $noDelete == 1 and $deleteOnly == 1 ) {
        print("ERROR: Option --nodelete and --deleteonly can not been set to true at the same time.\n");
        $optionError = 1;
    }

    if ( $opMode == 0 ) {
        $noDelete   = 0;
        $deleteOnly = 0;
    }
    elsif ( $opMode == 1 ) {
        $noDelete = 1;
    }
    elsif ( $opMode == 2 ) {
        $deleteOnly = 1;
    }

    my $deployUtils = DeployUtils->new();
    my $deployEnv   = $deployUtils->deployInit( $envPath, $version );
    $envPath = $deployEnv->{NAME_PATH};
    $version = $deployEnv->{VERSION};

    if ( not defined($envPath) or $envPath eq '' ) {
        $optionError = 1;
        print("ERROR: EnvPath not defined by option --envpath or Environment:NAME_PATH\n");
    }
    if ( not defined($version) or $version eq '' ) {
        $optionError = 1;
        print("ERROR: Version not defined by option --version or Environment:VERSION\n");
    }

    my $namePath = $deployEnv->{NAME_PATH};
    my $envName  = $deployEnv->{ENV_NAME};

    my $nodeInfo = $deployUtils->getNodeInfo($node);
    if ( not $nodeInfo ) {
        $optionError = 1;
        print("ERROR: Execute node json not defined by environment AUTOEXEC_NODE or option --node\n");
    }

    my $host  = $nodeInfo->{host};
    my $port  = $nodeInfo->{protocolPort};
    my $user  = $nodeInfo->{username};
    my $pass  = $nodeInfo->{password};
    my $insId = $nodeInfo->{resourceId};

    my $insUniqName = $nodeInfo->{nodeUniqName};

    if ( defined($pass) and $pass ne '' ) {
        $pass = $deployUtils->decryptPwd($pass);
    }

    $port = 22 if ( not defined($port) );

    my $lock            = DeployLock->new($deployEnv);
    my $mirrorAppLockId = $lock->lockMirror($DeployLock::READ);
    my $envAppLockId    = $lock->lockEnvApp($DeployLock::READ);
    my $insLockId = $lock->lockIns("$host:$port", $DeployLock::WRITE);
    
    END {
        local $?;
        if ( defined($lock) ) {
            $lock->unlockIns($insLockId);
            $lock->unlockEnvApp($envAppLockId);
            $lock->unlockMirror($mirrorAppLockId);
        }
    }

    my $serverAdapter = ServerAdapter->new();

    my $verInfo   = $serverAdapter->getEnvVer( $deployEnv, $version );
    my $verStatus = $verInfo->{status};
    my $buildNo   = $verInfo->{buildNo};
    my $isMirror  = $verInfo->{isMirror};

    if ( $verInfo->{status} ne 'released' ) {
        print("ERROR: $namePath version:$version is not released to $envName.\n");
        return 3;
    }

    if ( not defined($pdir) ) {
        if ( $isMirror == 1 ) {
            $pdir = 'mirror';
        }
        else {
            $pdir = 'appdist';
        }
    }

    my $dirInfo = $deployUtils->getDataDirStruct($deployEnv);
    my $verPath = $dirInfo->{$pdir};
    my $verRoot = $dirInfo->{appRoot};

    if ( defined($pdir) ) {
        if ( not defined($verPath) ) {
            print("ERROR: $pdir not valid, not in appsync|mirror|appbuild|version.\n");
            $optionError = 1;
        }
    }

    if ( $optionError == 1 ) {
        usage();
    }

    if ( not defined($ostype) or $ostype eq '' ) {
        $ostype = 'unix';
    }

    $src    = $deployUtils->charsetConv( $src,    'utf-8' );
    $dest   = $deployUtils->charsetConv( $dest,   'utf-8' );
    $expDir = $deployUtils->charsetConv( $expDir, 'utf-8' );

    my $envName = $deployEnv->{ENV_NAME};
    my $verRoot = $dirInfo->{appRoot};

    my $tmpDir = $deployEnv->{AUTOEXEC_HOME} . '/tmp';

    my $insDiffPath = "$verPath.ins";

    if ( not defined($src) or $src eq '' ) {
        $src = $verPath;
        if ( defined($insId) and $insId ne '' ) {
            my $insDiff = $insDiffPath . '/' . $insUniqName;
            if ( ( $pdir eq 'appdist' or $pdir eq 'mirror' ) and -d $insDiff ) {
                $src = "$src,$insDiff";
            }
        }
    }
    else {
        my $realSrc = realpath("$verPath/$src");
        if ( defined($insId) and $insId ne '' ) {
            my $insDiff = "$insDiffPath/$insUniqName/$src";
            if ( -d $insDiff ) {
                $src = "$realSrc,$insDiff";
            }
            else {
                $src = $realSrc;
            }
        }
    }

    my $hasError = 0;

    my $agentType  = $nodeInfo->{protocol};
    my $desc       = "$user\@$host:$dest";
    my $directDesc = 'to';
    print("INFO: Begin sync $src $directDesc $desc \n");
    eval {
        if ( $isPull == 1 ) {
            $directDesc = 'from';
            my $syncExec = new SyncRemote2Local( port => $port, tmpDir => $tmpDir, deleteOnly => $deleteOnly );
            $syncExec->upgradeFiles( $dest, $user, $pass, $host, $src, $expDir, $noDelete, $noAttrs, $followLinks, $agentType );
        }
        else {
            my $syncExec = new SyncLocal2Remote( port => $port, tmpDir => $tmpDir, deleteOnly => $deleteOnly );
            $syncExec->upgradeFiles( $ostype, $src, $user, $pass, $host, $insId, $dest, $expDir, $noDelete, $noAttrs, $followLinks, $addExecForNewFile, $agentType );
        }
    };
    if ($@) {
        $hasError = $hasError + 1;
        my $msg = $@;
        $msg =~ s/ at .*?$//;
        print($msg);
    }

    if ( $hasError == 0 ) {
        print("FINE: Sync $src $directDesc $desc success.\n");
    }
    else {
        print("ERROR: Sync $src $directDesc $desc failed.\n");
    }

    return $hasError == 0 ? 0 : 1;
}

exit main();

